from functools import lru_cache, cached_property

from manager import sprite_mgr


class Grid(object):
    # 格子
    def __init__(self, gid, min_x, max_x, min_y, max_y):
        self._gid = gid
        self.min_x = min_x
        self.max_x = max_x

        self.min_y = min_y
        self.max_y = max_y

        self.players = set()  # 角色ID

    @property
    def gid(self):
        return self._gid

    def __hash__(self):
        return hash(self.gid)

    def __str__(self):
        return "<{0}, {1}-{2}: {3}>,".format(self.gid, self.min_x, self.min_y, str(self.players))

    def add(self, eid):
        self.players.add(eid)

    def remove(self, eid):
        self.players.remove(eid)

    def get_player_ids(self):
        return list(self.players)


class Scene(object):
    # 场景，由多个格子组成
    def __init__(self, min_x, max_x, cnts_x, min_y, max_y, cnts_y):
        self.min_x = min_x
        self.max_x = max_x
        self.cnts_x = cnts_x  # X轴方向格子的数量

        self.min_y = min_y
        self.max_y = max_y
        self.cnts_y = cnts_y  # y轴方向格子的数量

        self.grids = {}

        self.map_entity = {}  # 实体对象
        self.init_grid()
        self.setup()

    def __str__(self):
        return self.str_val

    def setup(self):
        pass

    def init_all_tracks(self):
        pass

    def get_track(self, a, b):
        return []

    @cached_property
    def str_val(self):
        res = ""
        for y in range(self.cnts_y):
            for x in range(self.cnts_x):
                gid = y * self.cnts_x + x
                res += str(self.grids[gid])
            res += "\n"
        return res

    def init_grid(self):
        # 生成格子
        for y in range(self.cnts_y):
            for x in range(self.cnts_x):
                gid = y * self.cnts_x + x

                min_x = self.min_x + x * self.grid_width
                max_x = self.min_x + (x + 1) * self.grid_width
                min_y = self.min_y + y * self.grid_height
                max_y = self.min_y + (y + 1) * self.grid_height

                self.grids[gid] = Grid(gid, min_x, max_x, min_y, max_y)

    @cached_property
    def grid_width(self):
        # 每个格子在x轴方向的宽度
        return (self.max_x - self.min_x) // self.cnts_x

    @cached_property
    def grid_height(self):
        # 得到每个格子在Y轴方向高度
        return (self.max_y - self.min_y) // self.cnts_y

    def get_surround_grids_by_gid(self, gid, include_self=False):
        # 周边的格子对象
        surrounds = set()
        grid = self.grids[gid]
        y, x = divmod(grid.gid, self.cnts_x)
        for y_i, x_j in ((-1, 1), (-1, 0), (-1, -1), (0, -1), (0, 1), (1, 1), (1, 0), (1, -1)):
            cal_y = y + y_i
            cal_x = x + x_j
            if cal_x < 0 or cal_x >= self.cnts_x:
                continue
            if cal_y < 0 or cal_y >= self.cnts_y:
                continue

            cal_gid = cal_y * self.cnts_x + cal_x
            surrounds.add(self.grids[cal_gid])
        return surrounds

    def add_eid_grid(self, eid, gid):
        self.grids[gid].add(eid)

    def remove_eid_grid(self, eid, gid):
        self.grids[gid].remove(eid)

    def get_eids_by_gid(self, gid):
        return self.grids[gid].get_player_ids()

    def get_gid_by_pos(self, x, y):
        # 通过，x, y得到对应格子ID
        idx = (x - self.min_x) // self.grid_width
        idy = (y - self.min_y) // self.grid_height

        gid = idy * self.cnts_x + idx
        return gid

    def get_surround_eids_by_pos(self, x, y, include_self=False):
        # 根据一个坐标 得到 周边九宫格之内的全部的 玩家ID集合
        gid = self.get_gid_by_pos(x, y)
        grids = self.get_surround_grids_by_gid(gid)
        eids = []
        for grid in grids:
            eids.extend(grid.get_player_ids())
        if include_self:
            eids.extend(self.grids[gid].get_player_ids())
        return eids

    def add_to_grid_by_pos(self, eid, x, y):
        # 通过坐标 将eid 加入到一个格子中
        gid = self.get_gid_by_pos(x, y)
        grid = self.grids[gid]
        grid.add(eid)
        return gid

    def remove_to_grid_by_pos(self, eid, x, y):
        # 通过坐标 将eid remove到一个格子中
        gid = self.get_gid_by_pos(x, y)
        grid = self.grids[gid]
        grid.remove(eid)

    def update_pos(self, x, y, sprite=None, sprite_class=None):
        if not (sprite_class or sprite) or (sprite_class and sprite):
            raise AttributeError("Can't set both sprite and sprite_class")
        if sprite_class or sprite.scene_id not in self.map_entity:
            # 首次进入
            if sprite_class:
                eobj = sprite_mgr.get_level().add_sprite(sprite_class, (x, y))
            else:
                eobj = sprite
                x = sprite.pos.x
                y = sprite.pos.y
            update_eid = eobj.scene_id
            self.map_entity[update_eid] = eobj
            grip_id = self.add_to_grid_by_pos(update_eid, x, y)
            eids = self.get_surround_eids_by_pos(x, y)
            for eid in eids:
                ob = self.map_entity[eid]
                ob.enter(eobj)
        else:
            update_eid = sprite.scene_id
            eobj = self.map_entity[update_eid]
            eobj.last_x = eobj.pos.x
            eobj.last_y = eobj.pos.y
            old_gid = self.get_gid_by_pos(eobj.last_x, eobj.last_y)
            eobj.pos.x = x
            eobj.pos.y = y
            # 格子内移动
            new_gid = self.get_gid_by_pos(eobj.pos.x, eobj.pos.y)
            if old_gid == new_gid:
                eids = self.get_surround_eids_by_pos(x, y, True)
                for eid in eids:
                    self.map_entity[eid].move123(eobj)
            else:
                # 移动格子
                self.remove_eid_grid(update_eid, old_gid)
                self.add_eid_grid(update_eid, new_gid)
                old_surround_gids = self.get_surround_grids_by_gid(old_gid)
                new_surround_gids = self.get_surround_grids_by_gid(new_gid)

                # 新格子事件处理
                for grid in [grid for grid in new_surround_gids if grid not in old_surround_gids]:
                    for eid in grid.get_player_ids():
                        self.map_entity[eid].enter(eobj)

                # 老格子事件处理
                for grid in old_surround_gids:
                    if grid not in new_surround_gids:
                        for eid in grid.get_player_ids():
                            self.map_entity[eid].leave(eobj)
                    else:
                        for eid in grid.get_player_ids():
                            self.map_entity[eid].move123(eobj)
